Technical Note TN2051
Mac OS X QuickDraw Performance

目次

Mac OS X のウィンドウシステムは、それ以前の Mac OS のウィンドウシステムとはいくつかの点で根本的に異なります。QuickDraw に関する部分での主な変更点は、Mac OS X では、ウィンドウポートに対する描画は最終的には、スクリーン上ではなくウィンドウのバックバッファで行われるということです。この変更と、これを実現するのに必要だった、その基盤となる QuickDraw における変更は、新しく、興味深い形で、QuickDraw 描画コードのパフォーマンス特性に影響を与えています。このテクニカルノートは、それに関連した問題点のいくつかとその回避方法について説明します。

[2002 年 3 月 3 日]






トリプルバッファ処理の回避

描画は、スクリーンに対して直接行われるわけではなく、ウィンドウのバックバッファに対して行われるので、アプリケーション側でダブルバッファ処理を行う必要はなくなりました。かつては、ちらつきや表示の乱れを避けるために、描画をオフスクリーンの GWorld に合成して、その後 1 回の CopyBits 操作でスクリーンにコピーしなければならない場合もありました。Mac OS X の場合、この追加のバッファは不必要であるばかりか、むしろ、コンテンツをオフスクリーンからウィンドウのバックバッファに移動するのに余分なコピー操作が必要となるためパフォーマンスが低下します。Mac OS X ではダブルバッファ処理を行ってはいけません。

先頭に戻る



ダーティリージョンの更新を簡素化

QuickDraw がウィンドウのバックバッファのどの部分をスクリーンにフラッシュすべきかを知るためには、QuickDraw へのすべての呼び出しが、自身が対象とする領域をダーティリージョンとして記録する必要があります。描画シーケンスが、さらに大きな既知の形状の領域に含まれる複数の小さな項目で構成されている時は、通常、個別の QuickDraw ルーチンにダーティリージョンに段階的に追加させるよりも、領域全体をダーティリージョンとして 1 度に処理するのがベストです。そうすれば、各 QuickDraw ルーチンのダーティリージョン更新コードを省略できます。

QuickDraw の小さな操作のすべてが、より大きな矩形リージョン(bigRectRegion)に包含されていると仮定した場合、最初の QuickDraw 操作を行う前に QDSetDirtyRegion(port, bigRectRegion) を呼び出すことによって、矩形全域が 1 度にダーティリージョンとして処理され、ダーティリージョンを更新する小さな操作を行う複数の QuickDraw を呼び出す必要がなくなります。

ダーティリージョンを大きな矩形リージョンとして設定することで 2 つの目的が達成されます。1 つは、ダーティリージョンが必ず矩形になることです。これにより、その後のリージョン操作が大幅に簡素化されます。もう 1 つは、大きなリージョンが、個々の QuickDraw 描画呼び出しが対象とするダーティリージョンの全部をカバーするので、ダーティリージョンの更新は、単にリージョンの境界矩形の範囲内に含まれているかどうかをチェックするだけになります。

この手法は特に、複雑なベクタ画像の描画の高速化の際に便利に使えます。

先頭に戻る



QuickDraw の暗黙的な LockPortBits のバイパス

Mac OS X では、ウィンドウのバックバッファは、アプリケーションとウィンドウサーバーの間で共有されます。アプリケーションがバックバッファの内容にアクセスするには、ウィンドウサーバーを一時的にロックアウトする必要があります。QuickDraw のプリミティブは通常、どのような種類の開始/終了のシークエンスによっても囲まれることはないので、各 QuickDraw ルーチンは、描画前に暗黙的に LockPortBits を呼び出すことによって、確実にポートビットをロックする必要があります。連続的に多数の QuickDraw を呼び出すと、この暗黙的な LockPortBits のコストはきわめて大きくなります。

LockPortBits への呼び出しは幸いなことにネスト可能なので、LockPortBits と UnlockPortBits のペアでルーチンのシーケンス全体を囲むことによって、各 QuickDraw ルーチンで個別にポートビットのロック、アンロックを行う必要性がなくなります。

LockPortBits の呼び出しはネストできる上、ポートビットがすでにロックされている場合でもその操作全体を再実行しないようにできているので、LockPortBits と UnlockPortBits のペアでルーチンのシーケンス全体を囲むことによってネスト用の LockPortBits ビットを作成し、間にはさまれた QuickDraw 呼び出しを高速化します。

LockPortBits(GetWindowPort(window))
//  .. 自分の QD 描画シーケンス ...
UnlockPortBits();

これにより、QuickDraw の呼び出しシーケンスの全体に渡ってポートビットは 1 度だけロックされ、ネストされた暗黙の LockPortBits と UnlockPortBits はロックカウントをインクリメント/デクリメントするだけです。このようにすれば、作成するアプリケーションがビットをロックするためにかかるコストは、QuickDraw の呼び出しごとに 1 度ではなく、シークエンス全体において 1 度だけ発生することになり、パフォーマンス上の大きなボトルネックが解消されます。


注意:
最大 1、2 秒を超えてポートビットをロックし続けないことが重要です。システムのほかの機能にとっては、絶対的に必要な時間を超えてロックが維持されるのは好ましくありません。描画シーケンスが 2 秒を超える場合は、いくつかのセグメントに分割する必要があります。その際、それぞれのセグメントを LockPortBits と UnlockPortBits のペアで囲みます。


先頭に戻る



必要以上にスクリーンにフラッシュしない

ほとんどのアプリケーションでは、スムーズなアニメーションを実現するために、毎秒 30 フレーム以上フラッシュさせる必要はありません。これ以上フラッシュしても、CPU に余分なオーバーヘッドが生じるだけで、アニメーションの速度を低下させる可能性もあります。

フラッシュのタイミングを制御するには、30 fps を実現する時間基準を使います。

    /* 約 30 フレーム/秒 */
const UInt32 kMinNanosecsBetweenFlushes = 1E9 / 30;

static AbsoluteTime sLastFlush = { 0, 0 };

....

AbsoluteTime curTime = UpTime();
Nanoseconds  delta = AbsoluteDeltaToNanoseconds( curTime, sLastFlush );

if ( U64Compare( UnsignedWideToUInt64(delta),
          U64SetU(kMinNanosecsBetweenFlushes)) > 0)
{
  sLastFlush = curTime;
  QDFlushPortBuffer(port, NULL);
}

先頭に戻る



要約

Mac OS X では、QuickDraw の描画についてパフォーマンス上のいくつかの特性が新たに生じましたが、、ここで提供する情報は、開発者が、グラフィクスのパフォーマンスのボトルネックを解消するのに役立つでしょう。

先頭に戻る



ダウンロード

Acrobat gif

このテクニカルノートの PDF 版(36K)

ダウンロード


先頭に戻る